package de.saar.penguin.give2.server;

import de.saar.penguin.give2.server.alert.Alert;
import de.saar.penguin.give2.server.alert.AlwaysActivatedAlert;
import de.saar.penguin.give2.server.alert.EnteredRegionAlert;
import de.saar.penguin.give2.server.alert.FacingPositionAlert;
import de.saar.penguin.give2.server.alert.NotFacingPositionAlert;
import de.saar.penguin.give2.server.alert.ObjectBecomesVisibleAlert;
import de.saar.penguin.give2.server.alert.ReHasChangedAlert;
import de.saar.penguin.give2.server.alert.RememberRoomAlert;
import give.nlgserver.NlgServerException;
import java.util.*;
import give.world.*;
import give.formula.*;
import give.nlgserver.discretize.Discretizer;
import give.nlgserver.discretize.RegionDiscretizer;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Uwe Boltz
 *
 * The class InstructionGenerator generates Instruction objects
 * which are then handed over to the SaarNlgServer.
 * It contains what is necessary to generate instructions based on the plan from the Planner.
 * It needs a plan, a world and a discretizer to generate the
 * next Instruction using DescribedObject and REgen.
 */
public class InstructionGenerator {

    private SaarNlgSystem nlg; // Offers all necessary objects: World, Discretizer, BigBrother, InstructionGenerator, AlerGenerator
    // For easier access, we keep a class World and Discretizer Variable:
    private World w;
    private Discretizer d;
    private BigBrother bb;
    private REgen regen = new REgen();
    private RegionDiscretizer r;
//    private AlertGenerator AG;
    private int warningCounter = 0;
    private int longWarningCounter = 0;
    private String warning = "";
    private Room storedRoom;
    private List<String> visibleObjects = new ArrayList<String>();
    public String text = "couldn't create an instruction (Instruction.java)";
    public boolean isUsable = true;
    private String TAKE_TROPHY;
    private String TROPHY_ID;
    private String TROPHY_POSITION;
    //public List<Alert> alerts;
    //public static String currentInstructionTargetRegion = "";
    private Map<String, DescribedObject> describedObjects;
    public List<Alert> alerts = new ArrayList();
    int counter = 0;
    private List<String> alertedRooms = new ArrayList<String>();
    private HashMap<String, RoomDescription> roomDescriptions = new HashMap<String, RoomDescription>();
    private String lastReferredObject = null;
    private boolean lastReferredObjectConfirmed;

    /* Overloaded Constructors */
    /**
     * @param world
     * @param discretizer
     * @param describedObjects
     */
    public InstructionGenerator(SaarNlgSystem saarNlgSystem, Map<String, DescribedObject> describedObjects) {
        this.nlg = saarNlgSystem;
        this.w = saarNlgSystem.getWorld();
        this.d = saarNlgSystem.getDiscretizer();
        this.bb = saarNlgSystem.bigBrother;
        this.describedObjects = describedObjects;
        this.r = new RegionDiscretizer(w);
//        this.AG = new AlertGenerator(nlg);

    }

    /**
     * @param world
     * @param discretizer
     * @param text
     */
    public InstructionGenerator(SaarNlgSystem saarNlgSystem, String text) {
        this.nlg = saarNlgSystem;
        this.w = saarNlgSystem.getWorld();
        this.d = saarNlgSystem.getDiscretizer();
        this.text = text;
        this.r = new RegionDiscretizer(w);
//        this.AG = new AlertGenerator(nlg);

    }

    /**
     * @param world
     * @param discretizer
     * @param text
     * @param isUsable
     */
    public InstructionGenerator(World world, Discretizer discretizer, String text, boolean isUsable/*List<Alert> alerts*/) {
        this.w = world;
        this.d = discretizer;
        this.text = text;
        this.isUsable = isUsable;
        //this.alerts = alerts;
    }

    public void generateAlertsForRoom(PathRoom room) {
        if (!alertedRooms.contains(room.getId())) {

            for (RoomDescription description : room.getDescriptions()) {
                Alert a = new RememberRoomAlert(nlg, description);
                alerts.add(a);
            }

            alertedRooms.add(room.getId());
        }
    }

    public void addRoomDescription(RoomDescription description) {
        roomDescriptions.put(description.getRoom().getId(), description);
    }

    /**
     * removes Alerts similar to Alert a which occur in the same room than Alert a
     * @param a
     */
    public void removeSimilarAlerts(Alert a) {
        List<Alert> dummy = new ArrayList<Alert>(alerts);

        for (Alert alert : dummy) {
            if (alert != a && alert.equals(a) ) {
                alerts.remove(alert);
            }
        }
    }

    public void displayAlertedRooms() {
        System.out.print("Alerted Rooms: ");
        for (String room : alertedRooms) {
            System.out.print(room + " ");
        }
        System.out.println("");
    }

    /**
     * Generates random feedback messages for good or bad actions
     * @param isGood boolean : is the action good or bad
     * @return string : a feedback message
     */
    public String getFeedback(boolean isGood) {
        int number = (int) (Math.random() * 10);
        String feedback = "";

        if (isGood) {
            switch (number) {
                case 1:
                    ;
                case 2:
                    feedback = "Excellent! ";
                    break;
                case 3:
                    ;
                case 4:
                    feedback = "Nice! ";
                    break;
                case 5:
                    ;
                case 6:
                    feedback = "Alright! ";
                    break;
                case 7:
                    ;
                case 8:
                    feedback = "Good job! ";
                    break;
                case 9:
                    ;
                case 0:
                    feedback = "OK! ";
                    break;
            }
        } else {
            switch (number) {
                case 1:
                    ;
                case 2:
                    feedback = "That was not correct... Please, follow my instructions... ";
                    break;
                case 3:
                    ;
                case 4:
                    feedback = "No, not that. Look here. ";
                    break;
                case 5:
                    ;
                case 6:
                    feedback = "Oh, not exactly. ";
                    break;
                case 7:
                    ;
                case 8:
                    feedback = "Hmm, not quite. ";
                    break;
                case 9:
                    ;
                case 0:
                    feedback = "Nope, not that. OK, listen to me now... ";
                    break;
            }
        }

        return feedback;
    }

    /**
     * This method generates alarm tile warnings when relevant.
     * @param visible
     */
    private void generateWarning(List<String> visible) {
        /* Check if there are alarm tiles in the room ans send the warning if needed */
        warning = "";
        Room currentRoom = w.getRoomAtPosition(w.getPlayerPosition().getX(), w.getPlayerPosition().getZ());

        // Reset the counter when we change rooms
        if (storedRoom != null && storedRoom != currentRoom) {
            warningCounter = 0;
            storedRoom = currentRoom;
        }

        if (warningCounter < 3 && currentRoom != null) {

            //Display the long warning only 3 times then only send the short warning
            if (longWarningCounter < 2) {
                if (GIVEUtils.visibleActiveAlarmTile(visible, w)) {
                    //		warning = "The red part of the floor you see is an alarm, don't step on it! ";
                    warning = "Beware of the alarm you see! ";
                    warningCounter += 1;
                    longWarningCounter += 1;
                    alerts.add(new AlwaysActivatedAlert(nlg));
                } else if (GIVEUtils.roomContainsActiveAlarmTile(describedObjects.get(currentRoom.getName()), w)) {
                    //		warning = "The red part of the floor in this room is an alarm, don't step on it! ";
                    warning = "Beware of the alarm in this room! ";
                    warningCounter += 1;
                    longWarningCounter += 1;
                    alerts.add(new AlwaysActivatedAlert(nlg));
                }
            } else {
                if (GIVEUtils.visibleActiveAlarmTile(visible, w)) {
                    warning = "Beware of the alarm! ";
                    warningCounter += 1;
                    alerts.add(new AlwaysActivatedAlert(nlg));
                } else if (GIVEUtils.roomContainsActiveAlarmTile(describedObjects.get(currentRoom.getName()), w)) {
                    warning = "Beware of the alarm in this room! ";
                    warningCounter += 1;
                    alerts.add(new AlwaysActivatedAlert(nlg));
                }
            }

            storedRoom = currentRoom;
        }
    }

    /**
     * Creates the high level instructions like "go to the previous room" or
     * go to the room with the picture...
     * @param visible The visible objects
     * @return the instruction
     */
    public Instruction createHighLevelInstruction(List<String> visible) {

        //TODO fix plan

        List<Alert> dummy = new ArrayList(alerts);


        for (Alert a : dummy) {
            if (a.isDiesInCreateInstruction()) {
                alerts.remove(a);


            }
        }

        // Generate possible Alarm tile warning
        generateWarning(visible);
        //Some strings needed for the Instruction.
        String back = "Go back";
        String backto = back + " to ";
        String targetText = "";

        //The list of object types used for the instruction
        ObjectType[] targetTypes = {ObjectType.BED, ObjectType.DRESSER, ObjectType.TABLE,
            ObjectType.COUCH, ObjectType.CHAIR, ObjectType.PICTURE, ObjectType.FLOWER,
            ObjectType.LAMP, ObjectType.WINDOWLONG, ObjectType.WINDOWWIDE};//, ObjectType.SAFE};

        //The history which contains the visited rooms, the objects the player
        //has seen.
        History history = bb.getHistory();
        DescribedObject targetRoom = null;

        //The next target we'll manipulate and its Room
        String target = bb.getNextActionAtom().getArgumentAsString(0);

        if (bb.getNextActionAtom() == bb.currentPlan.get(bb.currentPlan.size() - 1)) {
            for (DescribedObject object : describedObjects.values()) {
                if (object.getType() == ObjectType.SAFE) {
                    targetRoom = describedObjects.get(object.getRoom());
                    break;
                }
            }
        } else {
            targetRoom = describedObjects.get(describedObjects.get(target).getRoom());
        }
        //The current Room
        String currentRoom = w.getRoomAtPosition(w.getPlayerPosition().getX(), w.getPlayerPosition().getZ()).getName();

        //If the current room is the same than the target room, we don't need high level navigation
        if (targetRoom.getId().equalsIgnoreCase(currentRoom)) {
            return createInstruction(visible);
        }

        //If the target room is the last room in the history, use the "last room" instruction
        if (targetRoom.getId().equals(history.getPreviousRoom())) {
            targetText = "the previous room.";
            bb.instructionTarget.targetType = BigBrother.InstructionTargetType.REGION;


            Instruction myInstruction = new Instruction(backto + targetText);

            String targetRoomRegion = "reg_" + targetRoom.getId();
            myInstruction.setHighLevel(true);
            myInstruction.setPath(GIVEUtils.getShortestPaths(r.getPositionName(w.getPlayerPosition()),
                    targetRoomRegion, w));
            myInstruction.targetRegion = targetRoomRegion;

            bb.news.endOfHighLevelTrack = false;
            bb.news.lostHighLevelTrack = false;
            nlg.log("HIGHLEVEL: Generated High Level Instruction");
            return myInstruction;
        }

        //Set a path of rooms which have to be visited to
        //reach the next manipulate target
        List<DescribedObject> roomPath = new ArrayList<DescribedObject>();

        for (int i = 0; i < bb.getNextActionAtomIndex(); i++) {
            if (bb.currentPlan.get(i).getPredicate().equals("move")) {
                String region1 = bb.currentPlan.get(i).getArgumentAsString(0);
                String region2 = bb.currentPlan.get(i).getArgumentAsString(1);
                String room1Id = r.getRegionInformation(region1).getRoom();
                String room2Id = r.getRegionInformation(region2).getRoom();

                //Add the rooms to the path if necessary and if they exist in the history

                if (!room1Id.equals(currentRoom)
                        && !roomPath.contains(describedObjects.get(room1Id))
                        && history.getPreviousRooms().contains(room1Id)) {
                    roomPath.add(describedObjects.get(room1Id));
                }
                if (!room2Id.equals(currentRoom)
                        && !roomPath.contains(describedObjects.get(room2Id))
                        && history.getPreviousRooms().contains(room2Id)) {
                    roomPath.add(describedObjects.get(room2Id));
                }
            }
        }

        // If the roomPath is empty we can't generate high level instruction
        if (roomPath.isEmpty()) {
            return createInstruction(visible);
        }

        /*
         * Go backward through the roomPath and pick the first room
         * which contains at least a distinctive seenObject
         */
        for (int i = roomPath.size() - 1; i >= 0; i--) {
            targetRoom = roomPath.get(i);


            if (GIVEUtils.debug()) {
                System.out.print("RoomPath (for): ");
                for (DescribedObject room : roomPath) {
                    System.out.print(room.getId() + " ");
                }
                System.out.println("target room : " + targetRoom.getId());
                System.out.println("Previous rooms : " + history.getPreviousRooms());
                System.out.println("Last room : " + history.getPreviousRoom());
            }


            typesfor:
            for (ObjectType targetType : targetTypes) {
                List<DescribedObject> targetRoomContent = targetRoom.getContentForType(targetType);
                if (targetRoomContent.size() > 0) {
                    for (String room : history.getPreviousRooms()) {
                        if (describedObjects.get(room).containsType(targetType)
                                && !targetRoom.getId().equals(room)) {
                            continue typesfor;
                        }
                    }

                    /*We found a room with a distinctive object.
                     * It's possible to generate an instruction
                     *
                     * If the room is the previously visited room, then
                     * tell the player to go to the previous room
                     * else use the objects.
                     *
                     *
                     * For the objects, we check if the plural is needed
                     */

                    if (GIVEUtils.rememberRooms()) {

                        // When using the remember rooms system, use the descriptions which was used for the alert
                        if (targetRoom.getId().equals(history.getPreviousRoom())) {
                            targetText = "the previous room.";
                        } else {
                            targetText = roomDescriptions.get(targetRoom.getId()).getDescription() + ".";
                        }
                    } else {
                        if (targetRoom.getId().equals(history.getPreviousRoom())) {
                            targetText = "the previous room.";
                        } else {
                            if (targetRoomContent.size() == 1) {
                                targetText = "the room with the " + targetType + ".";
                            } else {
                                targetText = "the room with the " + targetType + "s.";
                            }
                        }
                    }

                    bb.instructionTarget.targetType = BigBrother.InstructionTargetType.REGION;
                    Instruction myInstruction = new Instruction(backto + targetText);

                    String targetRoomRegion = "reg_" + targetRoom.getId();
                    myInstruction.setHighLevel(true);
                    myInstruction.setPath(GIVEUtils.getShortestPaths(r.getPositionName(w.getPlayerPosition()),
                            targetRoomRegion, w));
                    myInstruction.targetRegion = targetRoomRegion;

                    bb.news.endOfHighLevelTrack = false;
                    bb.news.lostHighLevelTrack = false;
                    nlg.log("HIGHLEVEL: Generated High Level Instruction");
                    return myInstruction;


                } else {
                    continue;
                }
            }
        }

        /*
         * There was no relevant objects in the rooms, now
         * let's try to use the wallpaper color to differentiate
         * the rooms.
         *
         * If we find some already visited rooms with the same wallpaper color
         * than the target room, then we can't create a high level instruction
         */

        roomsfor:
        for (int i = roomPath.size() - 1; i >= 0; i--) {
            targetRoom = roomPath.get(i);
            if (targetRoom.getId().equals(history.getPreviousRoom())) {
                targetText = "the previous room.";
                bb.instructionTarget.targetType = BigBrother.InstructionTargetType.REGION;
                Instruction myInstruction = new Instruction(backto + targetText);

                String targetRoomRegion = "reg_" + targetRoom.getId();
                myInstruction.setHighLevel(true);
                myInstruction.setPath(GIVEUtils.getShortestPaths(r.getPositionName(w.getPlayerPosition()),
                        targetRoomRegion, w));
                myInstruction.targetRegion = targetRoomRegion;


                bb.news.endOfHighLevelTrack = false;
                bb.news.lostHighLevelTrack = false;
                nlg.log("HIGHLEVEL: Generated High Level Instruction");
                return myInstruction;
            }

            for (String otherRoom : history.getPreviousRooms()) {
                if (targetRoom != describedObjects.get(otherRoom)
                        && targetRoom.getColor() == describedObjects.get(otherRoom).getColor()) {
                    continue roomsfor;
                }
            }

            /*
             * If the targetRoom is the previously visited room, tell the player
             * to go back to the previous room, else use the colors.
             */

            if (GIVEUtils.rememberRooms()) {
                if (targetRoom.getId().equals(history.getPreviousRoom())) {
                    targetText = "the previous room.";
                } else {
                    targetText = roomDescriptions.get(targetRoom.getId()).getDescription() + ".";
                }
            } else {
                if (targetRoom.getId().equals(history.getPreviousRoom())) {
                    targetText = "the previous room.";
                } else {
                    targetText = "the room with the " + targetRoom.getColor() + " wallpapers.";
                }
            }

            bb.instructionTarget.targetType = BigBrother.InstructionTargetType.REGION;
            Instruction myInstruction = new Instruction(backto + targetText);

            String targetRoomRegion = "reg_" + targetRoom.getId();
            myInstruction.setHighLevel(true);
            myInstruction.setPath(GIVEUtils.getShortestPaths(r.getPositionName(w.getPlayerPosition()),
                    targetRoomRegion, w));
            myInstruction.targetRegion = targetRoomRegion;


            bb.news.endOfHighLevelTrack = false;
            bb.news.lostHighLevelTrack = false;
            nlg.log("HIGHLEVEL: Generated High Level Instruction");
            return myInstruction;
        }

        return createInstruction(visible);
    }

    /**
     * Generates an instruction out of a plan and a list of visible objects.
     * also stores the target region of the instruction in currentTargetRegion for
     * public access */
    public Instruction createInstruction(List<String> visible) {
        setLastReferredObject(null, null);

        // Generate possible Alarm tile warning
        generateWarning(visible);


        /* remove all alerts that are to be removed now */
        List<Alert> dummy = new ArrayList(alerts);


        for (Alert a : dummy) {
            if (a.isDiesInCreateInstruction()) {
                alerts.remove(a);


            }
        }

        visibleObjects = visible; // make visibleObjects available globally

        TAKE_TROPHY = bb.currentPlan.get(bb.currentPlan.size() - 1).getPredicate();
        TROPHY_POSITION = bb.currentPlan.get(bb.currentPlan.size() - 1).getArgumentAsString(0);
        TROPHY_ID = TAKE_TROPHY.replace("take-", "");

        /* Re-initialize the target instruction type */
        bb.instructionTarget.targetType = BigBrother.InstructionTargetType.INITIALIZED;



        /* find the first manipulate or take within the plan and make it the next step.
         * If none is found, the first step of the original plan will stay the next step */
        Atom nextStep = bb.currentPlan.get(0);
        Atom targetAtom = nextStep;


        boolean foundSomething = false;


        for (Atom a : bb.currentPlan) {
            if (a.getPredicate().equals("manipulate") || a.getPredicate().equals("manipulate-stateless")
                    || a.getPredicate().equals(TAKE_TROPHY)) {
                nextStep = a;
                targetAtom = a;
                foundSomething = true;


                break;


            }
        }

        /*if a manipulate or take atom was found get target object from it*/
        DescribedObject targetObject = describedObjects.get(TROPHY_ID);


        if (foundSomething) {
            if (nextStep.getPredicate().equals(TAKE_TROPHY)) {
                targetObject = describedObjects.get(TROPHY_ID);


            } else {
                targetObject = describedObjects.get(nextStep.getArgumentAsString(0));


            }
        }

        /* check if target object and player are in the same room or visible through a door. If they are, try to refer to the
         * object, else make the player change rooms */
        if (GIVEUtils.getCurrentRoomContents(w, describedObjects).contains(targetObject.getId())) {
            Instruction myInstruction = addBeforeText(createInstructionFromTargetObject(targetAtom, targetObject), "manipulate: ");
            myInstruction.instructionPhrase = warning + myInstruction.instructionPhrase;


            return myInstruction;



        } else if (visibleObjects.contains(targetObject.getId())) {
            //target is visible but in another room, so it must be visible through an open door
            Instruction instr = createInstructionFromTargetObject(targetAtom, targetObject);
            instr.instructionPhrase = "Walk through the doorway and " + instr.instructionPhrase.toLowerCase();


            return instr;



        } else { //target object is not in the same room as the player

            alerts.add(new ObjectBecomesVisibleAlert(nlg, targetObject));
            /* the next button/trophy is in another room, so find the next door the
             * player has to walk through.
             *
             * first get all doors in the room*/
            List<String> objects = GIVEUtils.getCurrentRoomContents(w, describedObjects);
            List<String> doors = new ArrayList<String>();
            doors.clear(); //I'm not sure if this clear is necessary, but I'm playing it safe


            for (String name : objects) {
                DescribedObject o = describedObjects.get(name);


                if (o.getType() == ObjectType.DOOR) {
                    doors.add(name);  // if it's a door, remember it


                }
            }

            /* and now for every step of the plan and every door in the room, see if
             * there is a move-atom that tells you to go to the region the door
             * is in */
            foundSomething = false;
            loop:
            for (Atom a : bb.currentPlan) {
                if (!a.getPredicate().equals("move")) {
                    break;  //if we have reached a manipulate, we won't have to leave the room (for safety only, this case shouldn't occur)


                } else {
                    for (String name : doors) {
                        if (w.getPosition(name) != null && a.getArgumentAsString(1).equals(d.getPositionName(w.getPosition(name)))) {
                            targetObject = describedObjects.get(name);  //the first door in a region we have to go through is ours
                            foundSomething = true;
                            targetAtom = a;


                            break loop;


                        }
                    }
                }
            }

            /*if we found a door, create an instruction for it*/
            if (foundSomething) {

                Instruction myInstruction = addBeforeText(createInstructionFromTargetObject(targetAtom, targetObject), "door: ");
                myInstruction.instructionPhrase = warning + myInstruction.instructionPhrase;


                return myInstruction;


            }

        }


        //        /* if we haven't been able to return an instruction at this point,
        //         it is time to try low-level navigation as a last resort*/
        //        nextStep = bb.currentPlan.get(0);
        //        instruction.update("something went wrong in the low-level-fallback", nextStep);
        //        if (nextStep.getPredicate().equals("move")) {
        //            Position targetPosition = GIVEUtils.getCenterOfRegion(r,nextStep.getArgumentAsString(1));
        //            bb.instructionTarget.targetRegion = nextStep.getArgumentAsString(1);
        //            instruction = moveToPosition(targetPosition);
        ////this is a really bad hack, but at least it works:
        //            //instruction.instructionPhrase="That is the right door, go on";
        //        } else {
        //            /* if there is no more move-atom left for low-level navigation, we have
        //             * to refer to the next button even if it is not distinctive.
        //             * This should not happen if the rest of createInstruction works correctly*/
        //            targetObject = describedObjects.get(nextStep.getArgumentAsString(0));
        //             bb.instructionTarget.targetType = BigBrother.InstructionTargetType.ACTION;
        //             bb.instructionTarget.targetRegion = d.getPositionName(w.getPosition(targetObject.getId()));
        //            instruction =  new Instruction("Press the " + regen.olgarithm(describedObjects,targetObject,visibleObjects) + " closest to you.");
        //            //that the right object is the closest one is just a guess, but if we reach this point it means we can't do better
        //        }
        //
        //
        //
        //        /* Set the region of the current instruction.
        //         * Big Brother needs the region of instruction to decide when to trigger the instruction generatior.
        //         * The target type is set in the deep of the string generation.
        //         * the region is used for the instruction target type REGION and ANGLE.
        //         */
        //        bb.instructionTarget.targetObject = targetObject.getId();
        //        bb.instructionTarget.launchedOrientation = bb.getLastOrientation();
        //       //instruction.instructionPhrase="That's the right door, go on.";
        //       return add(instruction,"fallback: ");
        Position targetPosition = null;


        for (Atom step : bb.currentPlan) {

            if (step.getPredicate().equals("move")) {
                String currentRegion = step.getArgumentAsString(1);



                if (currentRegion.equals(bb.instructionTarget.targetRegion)) {
                    break;


                } else {
                    Position p = GIVEUtils.getCenterOfRegion(r, currentRegion);


                    if (GIVEUtils.checkVisibility(p, w.getPlayerPosition(), w)) {
                        targetPosition = GIVEUtils.getCenterOfRegion(r, currentRegion);
                    }
                    else {
                        targetPosition = null;
                    }
                }

            } else {
                break;


            }
        }

       
        if (targetPosition == null){
            try {
                targetPosition = GIVEUtils.getCenterOfRegion(r, bb.getTargetRegionName(nextStep));
            } catch (NlgServerException ex) {
                Logger.getLogger(InstructionGenerator.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        Instruction myInstruction = addBeforeText(moveToPosition(targetAtom, targetPosition), "nextVisible: " + counter);
        myInstruction.instructionPhrase = warning + myInstruction.instructionPhrase;


        return myInstruction;




    } //end of createInstruction

    private Instruction createInstructionFromTargetObject(Atom targetAtom, DescribedObject targetObject) {


        /* Set the region of the current instruction.
         * Big Brother needs the region of instruction to decide when to trigger the instruction generator.
         * The target type is set in the deep of the string generation.
         * the region is used for the instruction target type REGION and ANGLE.
         */
        if (targetObject.getId().equals(TROPHY_ID)) {
            bb.instructionTarget.targetRegion = TROPHY_POSITION;


        } else {
            bb.instructionTarget.targetRegion = d.getPositionName(w.getPosition(targetObject.getId()));


        }
        bb.instructionTarget.targetObject = targetObject.getId();
        bb.instructionTarget.launchedOrientation = bb.getLastOrientation();




        /*Check if target is visible. If not, try making the player move so it
         * becomes visible. If it is visible try getting the player closer to it.*/


        if (visibleObjects.contains(targetObject.getId())) {

            /*target is visible. Check if distinctive RE can be generated */
            String rexp = regen.olgarithm(describedObjects, targetObject, visibleObjects);


            if (!rexp.equals("oops")) {

                //RE is distinctive, create instruction
                setLastReferredObject(targetObject.getId(), rexp);
                return addBeforeText(selectAction(targetAtom, targetObject, rexp), "distinctive: ");



            } else {

                alerts.add(new ReHasChangedAlert(nlg, rexp, targetObject, regen, describedObjects));

                /* target is visible, but distinctive RE can't be generated. Check if player
                 * can walk closer to the target. */


                if (w.isNear(w.getPlayerPosition(), w.getPosition(targetObject.getId()))) {
                    // Only consider the near objects if the user is near to the target.
                    List<String> newVisibleObjects = new ArrayList<String>();


                    for (String object : visibleObjects) {
                        if (w.isNear(w.getPlayerPosition(), w.getPosition(object))) {
                            newVisibleObjects.add(object);


                        }
                    }
                    //first, try the algorithm with close visible objects
                    rexp = regen.olgarithm(describedObjects, targetObject, newVisibleObjects);


                    if (rexp.equals("oops")) {
                        //if it fails, use fallbackRE
                        rexp = regen.fallbackRE(targetObject);


                    }

                    setLastReferredObject(targetObject.getId(), rexp);

                    /* player is as close to the target as possible and can see it, but RE is
                     * still not distinctive. All we can do now is to make the player guess the
                     * right button*/ //		rexp = regen.fallbackRE(targetObject);
                    return addBeforeText(selectAction(targetAtom, targetObject, rexp), "close,not distinctive: ");



                } else { //player can still get closer to target

                    /* the player can see the target and get closer to it, but can't refer to
                     * it distinctively, so we use low-level navigation*/

                    return addBeforeText(moveToPosition(targetAtom, w.getPosition(targetObject.getId())), "move closer: ");



                }
            }

        } else { //target is not visible

            /*check if target can be made visible simply by turning around. If so, turn.
             * If not, find a position from which target is visible and make player go there*/
            if (GIVEUtils.checkVisibility(w.getPlayerPosition(), w.getPosition(targetObject.getId()), w)) {

                //make player turn.
                //for now I will not make a difference between turning and moving, because they are pretty similar.
                //I might change that later

                return addBeforeText(moveToPosition(targetAtom, w.getPosition(targetObject.getId())), "turn to: ");



            } else { //target can not be made visible by turning

                /* now look for the first region the player has to go through that is visible
                 * and make him move there*/

                Position targetPosition = w.getPlayerPosition();


                for (Atom step : bb.currentPlan) {

                    if (step.getPredicate().equals("move")) {
                        String currentRegion = step.getArgumentAsString(1);



                        if (currentRegion.equals(bb.instructionTarget.targetRegion)) {
                            break;


                        } else {
                            Position p = GIVEUtils.getCenterOfRegion(r, currentRegion);


                            if (GIVEUtils.checkVisibility(p, w.getPlayerPosition(), w)) {
                                targetPosition = GIVEUtils.getCenterOfRegion(r, currentRegion);


                            }
                        }

                    } else {
                        break;


                    }
                }

                return addBeforeText(moveToPosition(targetAtom, targetPosition), "nextVisible: ");


            }

        }

    }

    private Instruction selectAction(Atom targetAtom, DescribedObject targetObject, String rexp) {

        alerts.add(new ReHasChangedAlert(nlg, rexp, targetObject, regen, describedObjects));

        String again = "";


        if (bb.phraseShouldContainAgain(targetAtom, targetObject.getId())) {
            again = " again";


        }
        if (targetObject.getType() == ObjectType.BUTTON || targetObject.getType() == ObjectType.STATELESS_BUTTON) {
            bb.instructionTarget.targetType = BigBrother.InstructionTargetType.ACTION;
            //           alerts.add(AG.createNotFacingPosition(w.getPosition(targetObject.getId())));


            return new Instruction("Press " + rexp + again + ".", targetAtom, targetObject.getId());




        } else if (targetObject.getType() == ObjectType.TROPHY) {
            bb.instructionTarget.targetType = BigBrother.InstructionTargetType.ACTION;


            return new Instruction("Take " + rexp + again + ".", targetAtom, targetObject.getId());



        } else if (targetObject.getType() == ObjectType.DOOR) {
            bb.instructionTarget.targetType = BigBrother.InstructionTargetType.REGION;
            //             alerts.add(AG.createNotFacingPosition(w.getPosition(targetObject.getId())));
//                List<String> contents = GIVEUtils.getCurrentRoomContents(w, describedObjects);
//                for (String name:contents) {
//                    if (!name.equals(targetObject.getId()) && describedObjects.get(name).getType().getPredicate().equals("door")) {
//                        alerts.add(AG.createApproachingObject(name, "No, not this door. Go back."));
//                    }
//                }


            return new Instruction("Walk through " + rexp + again + ".", targetAtom, targetObject.getId());



        } else {
            bb.instructionTarget.targetType = BigBrother.InstructionTargetType.UNDEFINED;


            return new Instruction("Go to " + rexp + again + ".", targetAtom, targetObject.getId());


        }
    }

    private Instruction moveToPosition(Atom targetAtom, Position targetPosition) {
        double angle = w.getPlayerPosition().getOrientationTo(targetPosition).getAngleTo(w.getPlayerOrientation());
        Alert a = new FacingPositionAlert(nlg, targetPosition);
        alerts.add(a);
        alerts.add(new EnteredRegionAlert(nlg, d.getPositionName(targetPosition)));

        String s = "";


        if (GIVEUtils.debug()) {
            s = String.valueOf(angle);


        }

        if (Double.compare(angle, 2.25) > 0) {
            s = "Turn around.";
            bb.instructionTarget.targetAngle = 2.25;
            bb.instructionTarget.targetType = BigBrother.InstructionTargetType.ANGLE;


        } else if (Double.compare(angle, 0.75) > 0) {
            s = "Turn right.";
            bb.instructionTarget.targetAngle = 0.75;
            bb.instructionTarget.targetType = BigBrother.InstructionTargetType.ANGLE;


        } else if (Double.compare(angle, 0.5) > 0) {
            s = "Turn right a little.";
            bb.instructionTarget.targetAngle = 0.25;
            bb.instructionTarget.targetType = BigBrother.InstructionTargetType.ANGLE;


        } else if (Double.compare(angle, -0.5) > 0) {
            s = "Walk forward.";
            alerts.remove(a);
            alerts.add(new NotFacingPositionAlert(nlg, targetPosition));
            bb.instructionTarget.targetType = BigBrother.InstructionTargetType.REGION;


        } else if (Double.compare(angle, -0.75) > 0) {
            s = "Turn left a little.";
            bb.instructionTarget.targetAngle = -0.75;
            bb.instructionTarget.targetType = BigBrother.InstructionTargetType.ANGLE;


        } else if (Double.compare(angle, -2.25) > 0) {
            s = "Turn left.";
            bb.instructionTarget.targetAngle = -2.25;
            bb.instructionTarget.targetType = BigBrother.InstructionTargetType.ANGLE;


        } else {
            s = "Turn around.";
            bb.instructionTarget.targetAngle = -0.75;
            bb.instructionTarget.targetType = BigBrother.InstructionTargetType.ANGLE;


        } /*check if the target position can be seen from player position. If yes,
         * create Instruction to move there, if no create error message (this should
         * never happen in the final version)*/

        if (GIVEUtils.checkVisibility(w.getPlayerPosition(), targetPosition, w)) {
            return new Instruction(s, targetAtom, d.getPositionName(targetPosition));


        } else {
            return new Instruction("Walk out of the room.", null, d.getPositionName(targetPosition));
        }

    }
    private String lastConfirmedObject = null;

    private void setLastReferredObject(String r, String referringExpression) {
        lastReferredObject = r;
        lastConfirmedObject = null;

        if (r != null) {
            nlg.logReferenceAttempt(r);
            nlg.log("Referring expression for " + r + ": " + referringExpression);
        }

//        System.err.println("***** last referred: " + lastReferredObject);
    }

    public String getLastReferredObject() {
        return lastReferredObject;
    }

    public void confirmObject(String confirmed) {
        lastConfirmedObject = confirmed;
    }

    public String getLastConfirmedObject() {
        return lastConfirmedObject;
    }

    /******************************************************************************************
     *
     * The following methods are for testing only and shouldn't bother anyone too much
     *
     *
     */
    public Instruction addBeforeText(Instruction instruction, String s) {
        if (false) {
            instruction.instructionPhrase = (s + instruction.instructionPhrase);


        } // Add the warning (alarm tile)
        return instruction;

    }
}
